بواسطة Gabriel L. Manor يساعد Supabase على إضافة الاعتماد إلى التطبيق الخاص بك بفضل الدعم المدمج لرسائل البريد الإلكتروني و OAuth و الروبوتات السحرية، ولكن في حين أن Supabase Auth يتعامل مع المستخدمين، غالبا ما تحتاج إلى طبقة الاعتماد أيضًا. يوفر Supabase منصة دعم ممتازة مع auth وRow Level Security (RLS) المدمجة، وإدارة وخاصة تلك التي تعتمد على بل بعيد عن السهل. fine-grained permissions relationships between users and data قد تحتاج إلى الحد من الأفعال مثل تعديل أو حذف البيانات لأصحاب الموارد، أو منع المستخدمين من التصويت على المحتوى الخاص بهم، أو تزويد الحقوق المختلفة على مكونات المستخدم المختلفة. هذا التدريب يمر عبر كيفية تنفيذ في A التطبيق . Supabase authentication and authorization Next.js سنبدأ مع لتسجيل الدخول وإدارة الجلسة ، ثم إضافة استخدام ويحرم من خلال و a . . Supabase Auth authorization rules إدارة الوصول على أساس العلاقات (ReBAC) Supabase Edge Functions local Policy Decision Point (PDP) في النهاية، سيكون لديك تطبيق بحث مشترك في الوقت الحقيقي الذي يدعم كل من العمليات العامة والمنشورة، وسيتم تطوير نظام التأشيرة مرنة مع نمو التطبيق الخاص بك. ما نحن بصدده في هذه الخطوة، سنقوم بتطوير تطبيق الاستفتاء في الوقت الحقيقي باستخدام و هذا يظهر كلا من الاعتماد والتوصية في العمل. Supabase Next.js يتيح التطبيق للمستخدمين إنشاء الاستطلاعات، والانتخابات على الآخرين، وإدارة المحتوى فقط. لتسجيل الدخول / التسجيل وكيفية تطبيقها الذي يسيطر على من يمكنهم التصويت أو التعديل أو حذفها. Supabase Auth authorization policies سوف نستخدم الميزات الأساسية لـ Supabase- ، ، ، و و - متوافقة مع A تطبيقات لتنفيذ قواعد الوصول لكل مستخدم و لكل مصدر. Auth Postgres RLS Realtime Edge Functions Relationship-Based Access Control (ReBAC) تكنولوجيا Stack Supabase – Backend-as-a-service لخدمات قاعدة البيانات، والموثوقية، والتطبيقات في الوقت الحقيقي، والصفحات Next.js – نطاق Frontend لتطوير UI التطبيقات والطرق API Permit.io – (بالإضافة إلى ReBAC) لتحديد وتقييم منطق التأمين من خلال PDP Supabase CLI – لإدارة وتطوير وظائف Edge على المستوى المحلي والإنتاج القائمة التالي js المسمى.io مكتبة CLI متطلبات Node.js تثبيت محفظة حساب إمكانية حساب التعرف على React/Next.js ابتداءً من مشروع Repo ماذا يمكن أن يفعل هذا التطبيق؟ التطبيق الديموغرافي هو منصة الاستفتاء في الوقت الحقيقي التي تم إنشاؤها مع Next.js وSupabase ، حيث يمكن للمستخدمين إنشاء الاستفتاءات والانتخابات على الآخرين. أي مستخدم (أو غير معتبر) يمكن رؤية قائمة الاستطلاعات العامة فقط المستخدمين المقبولين يمكنهم إنشاء الاستطلاعات والتصويت لا يمكن للمستخدمين التصويت على استطلاع يخلقونه فقط إنشاء استطلاع يمكن إعداده أو حذفه تقييم Tutorial سوف نتبع هذه الخطوات العامة: إعداد مشروع Supabase ، schema ، auth ، و RLS بناء ميزات التطبيق الأساسية مثل إنشاء الاستطلاعات ووضع التصويت قواعد الوصية النموذجية تحدد الدور والقرارات في Permit.io إنشاء وظائف Supabase Edge لتنظيم المستخدمين، وتخصيص دورات، والتحقق من الحقوق تطبيق السياسات في مبنى التطبيقات باستخدام هذه الميزات الحدود دعنا نبدأ - Setting up Supabase in the Project إنشاء Supabase في المشروع تحديث: Clone the Starter Template لقد خلقنا بالفعل a ذاك مع كل الكود الذي تحتاجه للبدء حتى يمكننا التركيز على تنفيذ Supabase و Permit.io. ابتداء من المعبد GitHub يمكنك تشكيل المشروع من خلال إجراء الإجراءات التالية: git clone <https://github.com/permitio/supabase-fine-grained-authorization> بمجرد تكوين المشروع، قم بتنقل إلى مكتبة المشروع وتثبيت التوازنات: cd realtime-polling-app-nextjs-supabase-permitio npm install إنشاء مشروع جديد في Supabase للبدء : اذهب إلى https://supabase.com و تسجيل الدخول أو إنشاء حساب. انقر فوق "مشروع جديد" واضغط على اسم المشروع وكلمة المرور والمناطق. بمجرد إنشاء، قم بإنشاء إعدادات المشروع → API وتسجيل عنوان المشروع الخاص بك و Anon Key - سوف تحتاجها في وقت لاحق. تثبيت الوثائق والمعلومات في Supabase سوف نستخدم البريد الإلكتروني المدمج / كلمة المرور Auth Supabase: في صفحة الجانب، انقر فوق تصحيح → الموردين تفعيل خدمة البريد الإلكتروني (Optional) Disable email confirmation for testing, but keep it enabled for production إنشاء خطة قاعدة البيانات This app uses three main tables: ، و و • استخدامه في لوحة المفاتيح ، قم بإجراء التفاصيل التالية: polls options votes SQL Editor -- Create a polls table CREATE TABLE polls ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, question TEXT NOT NULL, created_by UUID REFERENCES auth.users(id) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), creator_name TEXT NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, ); -- Create an options table CREATE TABLE options ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, poll_id UUID REFERENCES polls(id) ON DELETE CASCADE, text TEXT NOT NULL, ); -- Create a votes table CREATE TABLE votes ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, poll_id UUID REFERENCES polls(id) ON DELETE CASCADE, option_id UUID REFERENCES options(id) ON DELETE CASCADE, user_id UUID REFERENCES auth.users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), UNIQUE(poll_id, user_id) ); إمكانية حماية مستوى الشبكة (RLS) تتيح لكل جدول وتحديد السياسات: RLS -- Polls policies ALTER TABLE polls ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view polls" ON polls FOR SELECT USING (true); CREATE POLICY "Authenticated users can create polls" ON polls FOR INSERT TO authenticated WITH CHECK (auth.uid() = created_by); -- Options policies ALTER TABLE options ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view options" ON options FOR SELECT USING (true); CREATE POLICY "Poll creators can add options" ON options FOR INSERT TO authenticated WITH CHECK ( EXISTS ( SELECT 1 FROM polls WHERE id = options.poll_id AND created_by = auth.uid() ) ); -- Votes policies ALTER TABLE votes ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view votes" ON votes FOR SELECT USING (true); CREATE POLICY "Authenticated users can vote once" ON votes FOR INSERT TO authenticated WITH CHECK ( auth.uid() = user_id AND NOT EXISTS ( SELECT 1 FROM polls WHERE id = votes.poll_id AND created_by = auth.uid() ) ); لاستخدام ميزات Supabase في الوقت الحقيقي: في صفحة الجانب ، قم بزيارة Table Editor لكل منهما ثلاثة جدول (الانتخابات، الخيارات، التصويتات): انقر على ثلاثة نقاط → تعديل جدول الترجمة "تسمح في الوقت الحقيقي" حفظ التغييرات تطبيق Supabase Email Authentication في التطبيق في هذا التطبيق الديموغرافي، يمكن لأي شخص مشاهدة قائمة الاستطلاعات المتاحة على التطبيق، على حد سواء، النشطة والمتتالية.للتعرف على تفاصيل الاستطلاعات، وإدارة، أو التصويت على أي استطلاعات، يجب أن يكون المستخدم متصلًا.نحن سنستخدم البريد الإلكتروني وكلمة المرور كوسيلة للتأكد من هذا المشروع.في مشروع Next.js الخاص بك، تخزين شهادات Supabase الخاص بك في : : .env.local NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key تحديث عنصر التسجيل الخاص بك لتعامل مع التسجيل والتسجيل عبر البريد الإلكتروني / كلمة المرور: import { useState } from "react"; import { createClient } from "@/utils/supabase/component"; const LogInButton = () => { const supabase = createClient(); async function logIn() { const { error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) { setError(error.message); } else { setShowModal(false); } } async function signUp() { const { error } = await supabase.auth.signUp({ email, password, options: { data: { user_name: userName, }, }, }); if (error) { setError(error.message); } else { setShowModal(false); } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (isLogin) { await logIn(); } else { await signUp(); } }; return ( <> <button onClick={() => setShowModal(true)} className="flex items-center gap-2 p-2 bg-gray-800 text-white rounded-md"> Log In </button> ... </> ); }; export default LogInButton; هنا، نحن نستخدم Supabase طريقة لتسجيل الدخول إلى المستخدم و طريقة لتسجيل المستخدم الجديد مع بريدك الإلكتروني وكلمة المرور. نحن أيضا تخزين اسم المستخدم في في متاجر المستخدم. signInWithPassword signUp user_name يمكنك أيضًا استخدام to log users out and redirect them: supabase.auth.signOut() import { createClient } from "@/utils/supabase/component"; import { useRouter } from "next/router"; const LogOutButton = ({ closeDropdown }: { closeDropdown: () => void }) => { const router = useRouter(); const supabase = createClient(); const handleLogOut = async () => { await supabase.auth.signOut(); closeDropdown(); router.push("/"); }; return ( ... ); }; export default LogOutButton; هنا، نحن نستخدم طريقة من Supabase لإزالة المستخدم وتسجيلها إلى الصفحة الرئيسية. signOut الاستماع إلى التغييرات في حالة التأكد من المستخدم تسمية التغييرات في حالة التأكد من المستخدم يتيح لنا تحديث UI بناءً على حالة التأكد من المستخدم. عرض / إخفاء عناصر UI مثل زر login / logout الحد من الوصول إلى صفحات محمية (مثل التصويت أو إدارة الاستطلاعات) تأكد من أن المستخدمين الوحيدين يمكنهم القيام بعمليات محدودة وسوف نستخدم استمع إلى هذه الأحداث وتحديث التطبيق بهذه الطريقة. supabase.auth.onAuthStateChange() In the Layout.tsx file: Track Global Auth State import React, { useEffect, useState } from "react"; import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Layout = ({ children }: { children: React.ReactNode }) => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); }; export default Layout; الحد من الوصول إلى صفحات محمية على صفحات مثل أو يجب عليك أيضًا الاستماع إلى التغييرات في حالة التأكد من أن المستخدمين غير التأكد من التأكد من الوصول إليها. poll details poll management هكذا تبدو في : : pages/polls/[id].tsx import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); setLoading(false); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); export default Page; ويستخدم نموذج مماثل في حيث ينبغي للمستخدمين رؤية الاستطلاعات الخاصة بهم فقط إذا كانوا متصلين: pages/polls/manage.tsx import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); if (!session?.user) { setLoading(false); } }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); }; export default Page; These patterns ensure your UI reflects the user’s current authentication status and form the basis for the authorization checks we'll add later. For example, you’ll later use this الهدف عند الدعوة إلى وظيفة Edge لتحديد ما إذا كان المستخدم يسمح بتصويت أو إدارة استطلاع معين. user checkPermission Building the Polling App Functionality مع Supabase تكوين وتأكيد العمل ، يمكننا الآن بناء الوظائف الأساسية لبرنامج الاستفتاء. إنشاء استطلاعات جديدة إرسال وإرسال الرسائل في الوقت الحقيقي تطوير نظام التصويت هذا يوفر لنا سلوك التطبيقات الأساسية التي سنحميها قريباً باستخدام الحقوق المحدودة. إنشاء استطلاعات جديدة يجب أن تكون المستخدمين متصلين لإنشاء الاستطلاعات. كل استطلاع يحتوي على سؤال، تاريخ انتهاء التكليف، ومجموعة من الخيارات. نحن أيضًا نشاهد من أطلقت الاستطلاعات حتى نتمكن في وقت لاحق من استخدام هذه العلاقة لمراقبة الوصول. داخل ، احصل على المستخدم المقبول ، واستخدم Supabase لإدخال الاستطلاع وإختياراته: NewPoll.tsx import React, { useEffect, useState } from "react"; import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const NewPoll = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (question.trim() && options.filter(opt => opt.trim()).length < 2) { setErrorMessage("Please provide a question and at least two options."); return; } // Create the poll const { data: poll, error: pollError } = await supabase .from("polls") .insert({ question, expires_at: new Date(expiryDate).toISOString(), created_by: user?.id, creator_name: user?.user_metadata?.user_name, }) .select() .single(); if (pollError) { console.error("Error creating poll:", pollError); setErrorMessage(pollError.message); return; } // Create the options const { error: optionsError } = await supabase.from("options").insert( options .filter(opt => opt.trim()) .map(text => ({ poll_id: poll.id, text, })) ); if (!optionsError) { setSuccessMessage("Poll created successfully!"); handleCancel(); } else { console.error("Error creating options:", optionsError); setErrorMessage(optionsError.message); } }; return ( ... ); }; export default NewPoll; سنقوم في وقت لاحق بإدعاء وظيفة الحجم هنا لتخصيص دور "الإنتاج" في Permit.io. إظهار وتصوير الاستطلاعات يتم تقسيم الاستطلاعات إلى (لأنه لم يحدث بعد) و (expired). You can fetch them using Supabase queries filtered by the current timestamp, and set up real-time subscriptions to reflect changes instantly. active past مثال من : : pages/index.tsx import { PollProps } from "@/helpers"; import { createClient } from "@/utils/supabase/component"; export default function Home() { const supabase = createClient(); useEffect(() => { const fetchPolls = async () => { setLoading(true); const now = new Date().toISOString(); try { // Fetch active polls const { data: activePolls, error: activeError } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .gte("expires_at", now) .order("created_at", { ascending: false }); if (activeError) { console.error("Error fetching active polls:", activeError); return; } // Fetch past polls const { data: expiredPolls, error: pastError } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .lt("expires_at", now) .order("created_at", { ascending: false }); if (pastError) { console.error("Error fetching past polls:", pastError); return; } setCurrentPolls(activePolls); setPastPolls(expiredPolls); } catch (error) { console.error("Unexpected error fetching polls:", error); } finally { setLoading(false); } }; fetchPolls(); // Set up real-time subscription on the polls table: const channel = supabase .channel("polls") .on( "postgres_changes", { event: "*", schema: "public", table: "polls", }, fetchPolls ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, []); return ( ... ); } مشاهدة وإدارة استطلاعات المستخدمين هنا ، نحن نتلقى النقاشات النشطة والخلفية من كما أننا نحدد ترخيصًا في الوقت الحقيقي للتحدث عن التغييرات table so that we can update the UI with the latest poll data. To differentiate between active and past polls, we are comparing the expiry date of each poll with the current date. polls polls تحديث The صفحة لتسجيل وتشغيل الاستطلاعات فقط التي تم إنشاؤها من قبل المستخدم: pages/manage.tsx import { PollProps } from "@/helpers"; const Page = () => { useEffect(() => { if (!user?.id) return; const fetchPolls = async () => { try { const { data, error } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .eq("created_by", user.id) .order("created_at", { ascending: false }); if (error) { console.error("Error fetching polls:", error); return; } setPolls(data || []); } catch (error) { console.error("Unexpected error fetching polls:", error); } finally { setLoading(false); } }; fetchPolls(); // Set up real-time subscription const channel = supabase .channel(`polls_${user.id}`) .on( "postgres_changes", { event: "*", schema: "public", table: "polls", filter: `created_by=eq.${user.id}`, }, fetchPolls ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [user]); return ( ... ); }; export default Page; هنا، نحن نبحث فقط عن الاستطلاعات التي تم إنشاؤها من قبل المستخدم، واستمع إلى التحديثات في الوقت الحقيقي. table so that the UI is updated with the latest poll data. polls وبالتالي، تحديث component so that if a logged-in user is the poll creator, icons for editing and deleting the poll will be displayed to them on the poll. PollCard import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const PollCard = ({ poll }: { poll: PollProps }) => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const supabase = createClient(); const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); setLoading(false); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... )} </Link> ); }; export default PollCard; So now, on a poll card, if the logged-in user is the poll creator, icons for editing and deleting the poll will be displayed to them. This allows the user to manage only their polls. تطوير نظام الاستفتاء منطق التصويت يفرض: 1 صوت لكل مستخدم Creators cannot vote on their own polls يتم تخزين التصويتات في جدول التصويت Results are displayed and updated in real time دعونا نلقي نظرة على كيف يعمل هذا في component: ViewPoll.tsx نحن بحاجة إلى ID المستخدم الحالي لتحديد ملصقات التصويت وتسجيل التصويت. Fetch the Logged-In User import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const ViewPoll = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() const fetchUser = async () => { const { data: { user }, } = await supabase.auth.getUser(); setUser(user); }; fetchUser(); }, []); بمجرد الحصول على المستخدم، نقوم بتحميل: Load Poll Details and Check Voting Status التقديرات نفسها (بما في ذلك الخيارات والتقديرات) إذا كان هذا المستخدم قد صوت بالفعل ونحن أيضا نسمع ذلك مرة أخرى في التحديثات في الوقت الحقيقي. useEffect(() => { if (!user) { return; } const checkUserVote = async () => { const { data: votes } = await supabase .from("votes") .select("id") .eq("poll_id", query.id) .eq("user_id", user.id) .single(); setHasVoted(!!votes); setVoteLoading(false); }; const fetchPoll = async () => { const { data } = await supabase .from("polls") .select( ` *, options ( id, text, votes (count) ) ` ) .eq("id", query.id) .single(); setPoll(data); setPollLoading(false); checkUserVote(); }; fetchPoll(); Listen for Real-Time Updates ونحن نتفق مع التغييرات في table, scoped to this poll. When a new vote is cast, we fetch updated poll data and voting status. votes const channel = supabase .channel(`poll-${query.id}`) .on( "postgres_changes", { event: "*", schema: "public", table: "votes", filter: `poll_id=eq.${query.id}`, }, () => { fetchPoll(); checkUserVote(); } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [query.id, user]); Handle the Vote Submission If the user hasn’t voted and is allowed to vote (we’ll add a permission check later), we insert their vote. const handleVote = async (optionId: string) => { if (!user) return; try { const { error } = await supabase.from("votes").insert({ poll_id: query.id, option_id: optionId, user_id: user.id, }); if (!error) { setHasVoted(true); } } catch (error) { console.error("Error voting:", error); } }; We calculate the total number of votes and a countdown to the expiration time. You can then use this to display progress bars or stats. Display the Poll Results if (!poll || pollLoading || voteLoading) return <div>Loading...</div>; // 6. calculate total votes const totalVotes = calculateTotalVotes(poll.options); const countdown = getCountdown(poll.expires_at); return ( ... ); }; export default ViewPoll; مع هذا الإعداد، نظام التصويت الخاص بك يعمل بشكل كامل. ولكن في الوقت الحالي، يمكن لأي شخص متصل أن يحاول تقنيًا التصويت - حتى في استطلاعاتهم الخاصة. using و لتنفيذ هذه القواعد. authorization checks Permit.io Supabase Edge Functions قبل أن نفعل ذلك، دعونا نلقي نظرة على نوع طبقة التأشيرة التي سنستخدمها. التعرف على ReBAC (مراقبة الوصول المرتبطة بالاتصال) يعمل Supabase بشكل جيد مع الاعتماد على الوظائف الأساسية على مستوى الجانب، ولكنها لا تدعم قواعد معقدة مثل: منع المستخدمين من التصويت على الاستطلاعات الخاصة بهم تخصيص دورات لكل الموارد (مثل "المصمم" لبحث محدد) Managing access via external policies لمساعدة هذه الأنواع من الحقوق المرتبطة بالروابط ، سنستخدم ReBAC مع Permit.io. is a model for managing permissions based on the relationships between users and resources. Instead of relying solely on roles or attributes (as in RBAC or ABAC), ReBAC determines access by evaluating how a user is connected to the resource they’re trying to access. Relationship-Based Access Control (ReBAC) إدارة الوصول على أساس العلاقات (ReBAC) في هذا التدريب ، نحن نطبق ReBAC على تطبيق الانتخابات: يستطيع المستخدم الذي خلق استطلاع أن يسيطر عليه (إصلاح / إزالة) لا يمكن للمستخدمين التصويت على الاستطلاع الخاص بهم Other authenticated users can vote once per poll من خلال نموذج هذه العلاقات في Permit.io ، يمكننا تحديد قواعد الوصول الخفيفة التي تتجاوز أمن مستوى الشبكة المدمج في Supabase (RLS). لمزيد من المعلومات حول ReBAC ، اقرأ . . أدوات ReBAC من Permit.io تصميم التحكم في الوصول بالنسبة لبرنامج الاستفتاء لدينا ، سنحدد: الموارد واحدة مع العمليات المحددة على الموارد: الاستطلاعات: إنشاء، قراءة، إزالة، تحديث. Two roles for granting permission levels based on a user’s relationship with the resources: Can perform and actions in polls. Can not , or actions in polls. authenticated: create read delete update Can , , , and actions in polls. Can perform and actions in votes. Cannot use on their own polls. creator: create read delete update read create create إعداد Permit.io Let’s walk through setting up the authorization model in Permit. إنشاء مشروع جديد في Permit.io اسمها مثل supabase-polling تحديد مصدر الاستطلاعات انقر فوق سياسة → قائمة الموارد انقر فوق "إنشاء مصدر" اسم مصدر الاستطلاعات، وإضافة العمليات: قراءة، إنشاء، تحديث، إزالة تثبيت ReBAC للموارد تحت "مخترعات ReBAC" تحدد الدور التالي: إنشاء معتبر اضغط على حفظ إنشاء مشروع جديد يرجى ملاحظة أننا نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع أن نستطيع. ، ، هذا لا يحتاج إلى هذا التدريب. admin editor user تحديد سياسات الوصول الى سياسات انقر فوق سياسة → سياسات استخدم الكاميرا المرئية لتحديد: يمكن قراءة الوثائق وتصنيعها من قبل مصمم الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الوثائق الو ارفع عينات الموارد ارفع عينات المراجعة الفردية كعينات المراجعة الفردية (نتم تثبيت هذا في وقت لاحق عند إنشاء المراجعات الجديدة) ارفع عينات المراجعة إلى المستخدمين لكل استطلاع (على سبيل المثال، user123 هو المصمم من الاستطلاع456) This structure gives us the power to write flexible access rules and enforce them per user, per poll. الآن بعد الانتهاء من الإعدادات الأصلية على لوحة المساعدة ، دعونا نستخدمها في تطبيقنا. إلى مشروع Supabase من خلال Edge Functions الذي: المسمى.io Sync المستخدمين الجديدين تخصيص دور المبدعين التحقق من الوصول على الطلب Setting up Permit in the Polling Application يقدم Permit طرق متعددة لتكثيف التكامل مع التطبيق، ولكن سنستخدم Containers PDP لهذا التدريب. تحتاج إلى استضافة المكونات عبر الإنترنت لتسجيلها في وظائف Supabase Edge. يمكنك استخدام خدمات مثل بمجرد تثبيتها ، قم بتخزين URL لملفك. طيران.com احصل على مفتاح API Permit الخاص بك عن طريق النقر على "مشاريع" في صفحة أدوات Permit، والتركيز إلى المشروع الذي أنتجته، والرد على الثلاث نقاط، واختيار "صورة مفتاح API". إنشاء Supabase Edge Function API لتسمية هي مثالية للتكامل مع خدمات طرف ثالث مثل Permit.io. سوف نستخدمها لتنفيذ قواعد ReBAC الخاصة بنا في الوقت الفعلي من خلال التحقق من ما إذا كانت المستخدمين يسمحون بتنفيذ إجراءات معينة في الاستطلاعات. Supabase Edge Functions Create Functions in Supabase ابدأ Supabase في مشروعك وخلق ثلاث وظائف مختلفة باستخدام command. These will be the starting point for your functions: supabase functions new npx supabase init npx supabase functions new syncUser npx supabase functions new updateCreatorRole npx supabase functions new checkPermission وهذا سوف يخلق الفوركس في أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولاً: أولا functions supabase تداول الفوركس الخيارات الثنائية ( ) syncUser.ts هذه الميزات تتحدث عن Supabase's عندما يتم تسجيل المستخدم الجديد، ونحن نقوم بتسجيل هويتهم إلى Permit.io وتخصيصهم الحد الأدنى دورة SIGNED_UP authenticated import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', } // Supabase Edge Function to sync new users with Permit.io Deno.serve(async (req) => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { event, user } = await req.json(); // Only proceed if the event type is "SIGNED_UP" if (event === "SIGNED_UP" && user) { const newUser = { key: user.id, email: user.email, name: user.user_metadata?.name || "Someone", }; // Sync the user to Permit.io await permit.api.createUser(newUser); await permit.api.assignRole({ role: "authenticated", tenant: "default", user: user.id, }); console.log(`User ${user.email} synced to Permit.io successfully.`); } // Return success response return new Response( JSON.stringify({ message: "User synced successfully!" }), { status: 200, headers: corsHeaders }, ); } catch (error) { console.error("Error syncing user to Permit: ", error); return new Response( JSON.stringify({ message: "Error syncing user to Permit.", "error": error }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } }); تخصيص دور المبدع ( ) updateCreatorRole.ts بمجرد أن يخلق المستخدم سؤالًا، يتم إرسال هذه الوظيفة إلى: تنزيل الاستطلاع كواحدة من مصدر Permit.io الجديد تخصيص الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة إلى الملفات المطروحة. تقييم الأوراق المالية ( ) checkPermission.ts هذه الوظيفة تعمل كمصممة - فإنه يحدد ما إذا كان المستخدم قادرًا على إجراء عملية معينة ( ، ، , (بالتحديد في زيارة معينة) create read update delete import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Authorization, x-client-info, apikey, Content-Type", "Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE", }; Deno.serve(async req => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { userId, operation, key } = await req.json(); // Validate input parameters if (!userId || !operation || !key) { return new Response( JSON.stringify({ error: "Missing required parameters." }), { status: 400, headers: { "Content-Type": "application/json" } } ); } // Check permissions using Permit's ReBAC const permitted = await permit.check(userId, operation, { type: "polls", key, tenant: "default", // Include any additional attributes that Permit needs for relationship checking attributes: { createdBy: userId, // This will be used in Permit's policy rules }, }); return new Response(JSON.stringify({ permitted }), { status: 200, headers: corsHeaders, }); } catch (error) { console.error("Error checking user permission: ", error); return new Response( JSON.stringify({ message: "Error occurred while checking user permission.", error: error, }), { status: 500, headers: { "Content-Type": "application/json" } } ); } }); اختبار محلي بدء خادم تطوير Supabase الخاص بك للتحقق من الوظائف المحلية: npx supabase start npx supabase functions serve يمكنك بعد ذلك ضرب وظائفك في: <http://localhost:54321/functions/v1/><function-name> مثال : <http://localhost:54321/functions/v1/checkPermission> إدماج التحقق من التأشيرة في UI Now that we’ve created our authorization logic with Permit.io and exposed it via Supabase Edge Functions, it’s time to enforce those checks inside the app’s components. في هذه الفقرة، سنقوم بتحديث مكونات UI الأساسية لإدخال هذه الميزات وتسمح أو تعطى تدابير المستخدم مثل التصويت أو إدارة الاستطلاعات على أساس التحقق من الحقوق. : تخصيص دور المؤلف بعد إنشاء الاستطلاع أندرويد.tsx أندرويد.tsx بعد إنشاء استطلاع وتخزينها إلى Supabase ، نحن نسمي وظيفة 2 : updateCreatorRole تنزيل الاستطلاع الجديد كميزة في Permit.io وَقَالَ أَبُو حَنِيفَةَ وَالشَّافِعِيُّ وَالْحَاكِمُ وَالْحَاكِمُ وَالْحَاكِمُ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَالْحَاكِمُ وَالْبَيْهَقِيُّ وَالْبَيْهَقِيُّ وَ الحد الأدنى من التصويت على أساس التأشيرة ViewPoll.tsx قبل تمكين المستخدم من التصويت في استطلاعات الرأي، نقوم بإنشاء وظيفة لتحديد ما إذا كان لديك الترخيص على هذه هي الطريقة التي نقوم بها لتنفيذ القواعد: checkPermission create votes “A creator cannot vote on their own poll.” Check voting permission: const [canVote, setCanVote] = useState(false); useEffect(() => { const checkPermission = async () => { if (!user || !query.id) return; try { const response = await fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "create", key: query.id, }), }); const { permitted } = await response.json(); setCanVote(permitted); } catch (error) { console.error("Error checking permission:", error); setCanVote(false); } }; checkPermission(); }, [user, query.id]); Disable vote buttons if user isn’t allowed: <button onClick={() => handleVote(option.id)} disabled={!user || !canVote}} className="w-full text-left p-4 rounded-md hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> {option.text} </button> Show a message if the user is not allowed to vote: {user && !canVote && ( <p className="mt-4 text-gray-600">You cannot vote on your own poll</p> )} : Control Access to Edit/Delete PollCard.tsx ونحن أيضًا نقوم بتحديد إجراءات إدارة الاستطلاعات (تعديل وإزالة) عن طريق التحقق من ما إذا كان المستخدم لديه أو إجابة على هذا السؤال. update delete Check management permissions: const [canManagePoll, setCanManagePoll] = useState(false); useEffect(() => { const checkPollPermissions = async () => { if (!user || !poll.id) return; try { // Check for both edit and delete permissions const [editResponse, deleteResponse] = await Promise.all([ fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "update", key: poll.id, }), }), fetch("/api/checkPermission", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "delete", key: poll.id, }), }), ]); const [{ permitted: canEdit }, { permitted: canDelete }] = await Promise.all([editResponse.json(), deleteResponse.json()]); // User can manage poll if they have either edit or delete permission setCanManagePoll(canEdit || canDelete); } catch (error) { console.error("Error checking permissions:", error); setCanManagePoll(false); } }; checkPollPermissions(); }, [user, poll.id]); Conditionally show management buttons: استبدال : {user?.id === poll?.created_by && ( مع : {canManagePoll && ( <div className="flex justify-start gap-4 mt-4"> <button type="button" onClick={handleEdit}> </button> <button type="button" onClick={handleDelete}> </button> </div> )} اختبار التكامل بمجرد دمجها ، يجب أن ترى السلوكات التالية في التطبيق: يمكن للمستخدمين المتصلين رؤية الاستطلاعات ولكنهم لا يتفاعلون يمكن للمستخدمين المؤهلين التصويت على الاستطلاعات التي لم تخلقها المبدعين لا يمكنهم التصويت على الاستطلاعات الخاصة بهم Only creators see options on their polls edit/delete يجب أن تكون قادرًا على رؤية التغييرات في التطبيق من خلال الذهاب إلى المتصفح.في الشاشة الرئيسية، يمكن للمستخدمين رؤية قائمة الاستطلاعات النشطة والمتتالية، سواء كانت متصلة أو غير متصلة. بعد تسجيل الدخول، يمكن للمستخدم مشاهدة تفاصيل الاستطلاع وتصحيحها، ومع ذلك، إذا كان المستخدم مصمم الاستطلاع، فلن يكون قادرًا على التصحيح. النتيجة في هذه الدورات، نستعرض كيفية تنفيذ في عالم حقيقي التطبيق . Supabase authentication and authorization Next.js بدأنا بإنشاء for login and signup, created a relational schema with Row Level Security, and added dynamic authorization logic using . With the help of و a وبدأت عمليات التحقق من التأشيرة مباشرة من المفتشية. Supabase Auth ReBAC Supabase Edge Functions Policy Decision Point (PDP) من خلال الجمع بين مع التحكم المرونة في الوصول ، تمكننا من: Supabase Auth تأكيد المستخدمين عبر البريد الإلكتروني وكلمة المرور الحد من التصويت وإدارة الاستطلاعات للمستخدمين المقبولين منع المصممين من التصويت على الاستطلاعات الخاصة بهم تخصيص وتقييم دورات المستخدم على أساس العلاقات مع البيانات تتيح لك هذه الإعدادات أساساً قابلة للتوسع لإنشاء التطبيقات التي تتطلب أيضًا الاعتماد والتوصية الصغيرة. اقرأ المزيد Permit.io إرشادات ReBAC إمكانية الحصول على تأشيرة + مؤشر عناصر الترخيص: UI المدمج لإدارة الخصائص تصفح البيانات مع الترخيص حسابات التحقيقات هل لديك أسئلة؟ الانضمام لدينا حيث تقوم مئات المطورين بتصنيع وتناقش التمويل. مجتمع Slack مجتمع Slack